package com.mo.tools.config.redis;

import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.reflect.MethodSignature;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.LocalVariableTableParameterNameDiscoverer;
import org.springframework.expression.EvaluationContext;
import org.springframework.expression.Expression;
import org.springframework.expression.ExpressionParser;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.expression.spel.support.StandardEvaluationContext;
import org.springframework.stereotype.Component;
import org.springframework.util.ObjectUtils;

import java.lang.reflect.Method;
import java.time.LocalDate;
import java.util.LinkedHashMap;
import java.util.concurrent.TimeUnit;

/**
 * author: Xingfu.Yang
 * date: 2024/2/22 10:28
 */
@Aspect
@Component
@Slf4j
public class RedissonDistributedLockAspect {

    @Autowired
    private RedissonClient redisson;
    private static final String REDIS_LOCK_PREFIX = "redis-lock:";

    @Around("@annotation(redissonDistributedLock)")
    public Object doAround(ProceedingJoinPoint joinPoint, RedissonDistributedLock redissonDistributedLock) throws Throwable {
        String lockName = redissonDistributedLock.name();
        long lockTime = redissonDistributedLock.lockTime();
        long waitTime = redissonDistributedLock.waitTime();
        boolean autoUnLock = redissonDistributedLock.autoUnLock();
        if (ObjectUtils.isEmpty(lockName)) {
            MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
            Method method = methodSignature.getMethod();
            String className = method.getDeclaringClass().getName();
            String methodName = method.getName();
            LinkedHashMap<String, Object> pramsMap = resolveParams(joinPoint);
            String key = String.join("_", pramsMap.keySet());
            lockName = REDIS_LOCK_PREFIX + LocalDate.now() + ":" + String.join("_", className, methodName, key);
        }
        if (isEl(lockName)) {
            lockName = getByEl(lockName, joinPoint);
            lockName = REDIS_LOCK_PREFIX + LocalDate.now() + ":" + lockName;
        }
        RLock lock = redisson.getLock(lockName);

        boolean locked = false;
        try {
            // 尝试获取分布式锁
            locked = lock.tryLock(waitTime, lockTime, TimeUnit.MILLISECONDS);
            if (!autoUnLock && !locked) {
                int minutes = (int) Math.ceil((double) lockTime / 1000 / 60);
                minutes = minutes == 0 ? 1 : minutes;
                log.error("分布式锁未释放，请求失败 => redis-key: {} locked-time: {}, ", lockName, lockTime);
                throw new RuntimeException(String.format("任务正在执行，请%s分钟后再试", minutes));
            }
            if (locked) {
                log.info("分布式锁加锁成功 => redis-key: {} locked-time: {}", lockName, lockTime);
                // 获取锁成功，执行业务逻辑
                return joinPoint.proceed();
            } else {
                log.error("获取分布式锁失败 => redis-key: {} locked-time: {}", lockName, lockTime);
                throw new RuntimeException("任务重复执行，请稍后重试");
            }
        } finally {
            if (locked) {
                // 释放分布式锁
                lock.unlock();
                log.info("分布式锁释放成功 => redis-key: {} locked-time: {}", lockName, lockTime);
            }
        }
    }

    /**
     * 处理参数
     *
     * @param joinPoint
     * @return
     */
    private LinkedHashMap<String, Object> resolveParams(ProceedingJoinPoint joinPoint) {
        MethodSignature signature = (MethodSignature) joinPoint.getSignature();
        Method method = signature.getMethod();
        Object[] arguments = joinPoint.getArgs();
        String[] paramNames = getParameterNames(method);

        LinkedHashMap<String, Object> params = new LinkedHashMap<>();
        for (int i = 0; i < arguments.length; i++) {
            params.put(paramNames[i], arguments[i]);
        }
        return params;
    }

    /**
     * 解析 SpEL 表达式并返回其值
     */
    private String getByEl(String el, ProceedingJoinPoint point) {
        Method method = ((MethodSignature) point.getSignature()).getMethod();
        String[] paramNames = getParameterNames(method);
        Object[] arguments = point.getArgs();

        ExpressionParser parser = new SpelExpressionParser();
        Expression expression = parser.parseExpression(el);
        EvaluationContext context = new StandardEvaluationContext();
        for (int i = 0; i < arguments.length; i++) {
            context.setVariable(paramNames[i], arguments[i]);
        }
        return expression.getValue(context, String.class);
    }

    /**
     * 获取方法参数名列表
     */
    private String[] getParameterNames(Method method) {
        LocalVariableTableParameterNameDiscoverer u = new LocalVariableTableParameterNameDiscoverer();
        return u.getParameterNames(method);
    }

    /**
     * 是否el*
     *
     * @param str
     * @return
     */
    private boolean isEl(String str) {
        return str.contains("#");
    }
}
